package org.jetlinks.pro.tenant;

import lombok.AllArgsConstructor;
import lombok.NonNull;
import org.apache.commons.collections4.CollectionUtils;
import org.hswebframework.ezorm.core.Conditional;
import org.hswebframework.ezorm.core.NestConditional;
import org.hswebframework.web.authorization.DimensionType;
import org.jetlinks.pro.assets.AssetPermission;
import org.jetlinks.pro.assets.AssetType;
import org.jetlinks.pro.assets.*;
import org.jetlinks.pro.tenant.dimension.TenantDimensionType;
import org.jetlinks.pro.tenant.supports.MultiTenantMember;
import org.jetlinks.pro.tenant.term.MultiAssetsTerm;
import org.jetlinks.pro.topic.Topics;
import org.springframework.core.Ordered;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Nonnull;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@AllArgsConstructor
class TenantAssetsHolder implements AssetsHolder, Ordered {

    private final TenantMember member;

    private final TenantAssetManager assetManager;

    private final Set<String> allowAllAssets;
    private final Set<String> ignoreAssets;
    private final Set<String> dimensionAssets;

    public boolean isAssetDimension(DimensionType type) {
        return dimensionAssets != null && dimensionAssets.contains(type.getId());
    }

    @Override
    public Flux<DimensionBinding> getDimensionBindings() {
        if (member instanceof MultiTenantMember) {
            return Flux
                .fromIterable(((MultiTenantMember) member).getMembers())
                .map(TenantDimensionBinding::new);
        }
        return Flux.just(new TenantDimensionBinding(member));
    }

    @Override
    public Flux<DimensionBinding> getDimensionBindings(DimensionType targetType) {
        if (TenantDimensionType.any(targetType)) {
            return getDimensionBindings();
        }
        if (isAssetDimension(targetType)) {
            return member
                .getAssets(AssetType.of(targetType.getId()))
                .map(asset -> DimensionBinding.of(targetType, asset.getAssetId(), 0));
        }
        return Flux.empty();
    }

    @Override
    public Flux<DimensionBinding> getDimensionBindings(DimensionType targetType, Collection<?> targetId) {

        if (isAssetDimension(targetType)) {
            return member
                .getAssets(AssetType.of(targetType.getId()), targetId)
                .map(asset -> DimensionBinding.of(targetType, asset.getAssetId(), 0));
        }
        return AssetsHolder.super.getDimensionBindings(targetType, targetId);
    }

    @Override
    public Flux<String> refactorTopic(String topic) {
        if (member instanceof MultiTenantMember) {
            return Flux
                .fromIterable(((MultiTenantMember) member).getMembers())
                .map(mem -> createTopic(mem, topic));
        }
        return Flux.just(createTopic(member,topic));
    }

    private String createTopic(TenantMember member, String topic) {
        if (member.isAllDataAccess()) {
            return Topics.tenant(member.getTenant().getId(), topic);
        }
        return Topics.tenantMember(member.getUserId(), topic);
    }

    @Override
    public Flux<AssetBinding> getBindings(AssetType assetType) {
        if (isIgnore(assetType.getId())) {
            return Flux.empty();
        }
        return member
            .getAssets(assetType)
            .cast(AssetBinding.class);
    }

    @Override
    public Flux<AssetBinding> getBindings(AssetType assetType,
                                          Collection<?> idList) {
        if (isIgnore(assetType.getId())) {
            return Flux.empty();
        }
        return member
            .getAssets(assetType, idList)
            .cast(AssetBinding.class);
    }

    @Override
    public Flux<AssetBinding> getBindings(DimensionType targetType,
                                          AssetType assetType) {
        if (TenantDimensionType.any(targetType) && !isIgnore(assetType.getId())) {
            return member
                .getAssets(assetType)
                .cast(AssetBinding.class);
        }
        return Flux.empty();
    }

    @Override
    public Flux<AssetBinding> getBindings(DimensionType targetType,
                                          Collection<?> targetId,
                                          AssetType assetType) {
        if (TenantDimensionType.any(targetType) && !isIgnore(assetType.getId())) {
            return member
                .getAssets(assetType, targetId)
                .cast(AssetBinding.class);
        }
        return Flux.empty();
    }

    @Override
    public Flux<AssetBinding> getBindings(DimensionType targetType,
                                          Collection<?> targetIdList,
                                          AssetType assetType,
                                          Collection<?> assetIdList) {
        if (TenantDimensionType.any(targetType) && !isIgnore(assetType.getId())) {
            return member
                .getAssets(assetType, assetIdList)
                .cast(AssetBinding.class);
        }
        return Flux.empty();
    }

    @Nonnull
    @Override
    public Mono<Void> bindAssets(@NonNull DimensionType targetType,
                                 @NonNull AssetType assetType,
                                 @NonNull Collection<?> assetId) {
        if (TenantDimensionType.any(targetType) && !isIgnore(assetType.getId())) {
            return member
                .bindAssets(assetType, assetId);
        }
        return Mono.empty();
    }

    @Nonnull
    @Override
    public Mono<Void> bindAssets(@NonNull String targetType,
                                 @NonNull String targetId,
                                 @NonNull String assetType,
                                 @NonNull Collection<?> assetId) {
        if (TenantDimensionType.any(targetType) && !isIgnore(assetType)) {
            return member
                .bindAssets(assetType, assetId);
        }
        return Mono.empty();
    }

    @Nonnull
    @Override
    public Mono<Void> bindAssets(@NonNull String assetType, @NonNull Collection<?> assetId) {
        if (isIgnore(assetType)) {
            return Mono.empty();
        }
        return member
            .bindAssets(assetType, assetId);
    }

    @Nonnull
    @Override
    public Mono<Void> unbindAssets(@NonNull String assetType, @NonNull Collection<?> assetId) {
        if (isIgnore(assetType)) {
            return Mono.empty();
        }
        return member
            .unbindAssets(assetType, assetId);
    }

    @Nonnull
    @Override
    public Mono<Void> unbindAssets(@NonNull String targetType,
                                   @NonNull String targetId,
                                   @NonNull String assetType,
                                   @NonNull Collection<?> assetId) {
        if (TenantDimensionType.any(targetType) && !isIgnore(assetType)) {
            return member
                .unbindAssets(assetType, assetId);
        }
        return Mono.empty();
    }

    @Override
    public @NonNull Mono<Boolean> hasPermission(@NonNull String assetType,
                                                @NonNull Collection<?> assetId,
                                                boolean allowAssetNotExist,
                                                @Nonnull @NonNull AssetPermission... permission) {
        //不判断权限返回empty
        if (isIgnore(assetType)) {
            return Mono.empty();
        }
        if (isAllowAll(assetType)) {
            Set<?> nonMatch = new HashSet<>(assetId);
            return Flux
                .fromIterable(getTenantId())
                .flatMap(tenantId -> assetManager
                    .getTenantAssets(tenantId, AssetType.of(assetType), null, assetId)
                    .doOnNext(asset -> nonMatch.remove(asset.getAssetId()))
                    .all(asset -> asset.hasPermission(permission)))
                //如果还存在没有匹配的数据,说明可能数据不存在？
                .filter(ignore -> nonMatch.isEmpty())
                .switchIfEmpty(Mono.defer(() -> {
                    if (!allowAssetNotExist) {
                        return Mono.just(false);
                    }
                    return this
                        .assetManager
                        .getAssets(AssetType.of(assetType), nonMatch)
                        .hasElements()
                        //数据不存在才有权限
                        .map(exist -> !exist);
                }))
                .any(Boolean::booleanValue);
        }
        return member.hasPermission(assetType, assetId, allowAssetNotExist, permission);
    }

    @Override
    public @NonNull <T> Flux<T> filter(@NonNull Flux<T> source,
                                       @NonNull AssetType assetType,
                                       @NonNull Function<T, ?> assetIdMapper,
                                       @Nonnull @NonNull AssetPermission... permission) {
        if (isIgnore(assetType.getId()) || isAllowAll(assetType.getId())) {
            return source;
        }
        return member.filter(source, assetType, assetIdMapper, permission);
    }

    @Override
    public <T extends Conditional<T>> NestConditional<T> injectConditional(NestConditional<T> conditional, String assetType, String idProperty) {
        if (isIgnore(assetType)) {
            return conditional;
        }
        //这个资产只要是在租户里就能查看。
        if (isAllowAll(assetType)) {
            return conditional.accept(idProperty, MultiAssetsTerm.ID, MultiAssetsTerm.from(assetType, getTenantId()));
        }
        return conditional.accept(idProperty, MultiAssetsTerm.ID, MultiAssetsTerm.from(assetType, member));
    }

//    @Override
//    public <T extends Conditional<T>> NestConditional<T> injectDimensionConditional(NestConditional<T> conditional,
//                                                                                    String dimensionType,
//                                                                                    String idProperty) {
//        if (TenantDimensionType.tenant.isSameType(dimensionType)) {
//            List<String> id = (member instanceof MultiTenantMember)
//                ? ((MultiTenantMember) member)
//                .getTenants()
//                .stream()
//                .map(Tenant::getId)
//                .collect(Collectors.toList())
//                : Collections.singletonList(member.getTenant().getId());
//            return conditional.accept(idProperty, TermType.in, id);
//        } else if (TenantDimensionType.tenantMember.isSameType(dimensionType)) {
//            return conditional.accept(idProperty, TenantMemberTermBuilder.ID, member.getUserId());
//        }
//        return conditional;
//    }

    @Override
    public int getOrder() {
        //有全部数据权限设置优先级为最高.
        //场景: 租户管理员可以看到租户全部数据,无论管理员是否有其他维度的资产权限
        return member.isAllDataAccess()
            ? Ordered.HIGHEST_PRECEDENCE
            : ORDER_DEFAULT;
    }

    private boolean isAllowAll(String assetType) {
        return !CollectionUtils.isEmpty(allowAllAssets)
            && (allowAllAssets.contains(assetType) || allowAllAssets.contains("*"));
    }

    private boolean isIgnore(String assetType) {
        return !CollectionUtils.isEmpty(ignoreAssets)
            && (ignoreAssets.contains(assetType) || ignoreAssets.contains("*"));
    }

    private List<String> getTenantId() {
        return member instanceof MultiTenantMember
            ? ((MultiTenantMember) member)
            .getTenants()
            .stream()
            .map(Tenant::getId)
            .collect(Collectors.toList())
            : Collections.singletonList(member.getTenant().getId());
    }

    @AllArgsConstructor
    static class TenantDimensionBinding implements DimensionBinding {

        private final TenantMember member;

        @Override
        public DimensionType getTargetType() {
            return TenantDimensionType.tenant;
        }

        @Override
        public String getTargetId() {
            return member
                .getTenant()
                .getId();
        }

        @Override
        public int getPriority() {
            return member.isAllDataAccess()
                ? Ordered.LOWEST_PRECEDENCE
                : ORDER_DEFAULT;
        }
    }

}
